﻿#region LGPL License

/*
OpenSearchNet
Copyright (c) 2014, Brad Hards <bradh@frogmouth.net>, All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3.0 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library.
*/

#endregion LGPL License

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

[assembly: CLSCompliant(true)]

namespace OpenSearch.Osdd
{
    /// <summary>
    /// OpenSearch Description Document
    /// </summary>
    [CLSCompliant(true)]
    public class OpenSearchDescriptionDocument
    {
        private string shortName = "";
        private string description = "";
        private string contact = "";
        private string longName = "";
        private string developer = "";
        private bool adultContent = false;
        private Collection<string> tags = new Collection<string>();
        private Collection<Url> urls = new Collection<Url>();
        private Collection<Image> images = new Collection<Image>();
        private Collection<Query> queries = new Collection<Query>();
        private Collection<string> languages = new Collection<string>();
        private Collection<string> outputEncodings = new Collection<string>();
        private Collection<string> inputEncodings = new Collection<string>();
        private SyndicationRights syndicationRight = new SyndicationRights();

        /// <summary>
        /// Streamed content based Constructor
        /// </summary>
        /// <param name="stream">stream to load the description document contents from</param>
        public OpenSearchDescriptionDocument(System.IO.Stream stream)
        {
            using (System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(stream))
            {
                useReader(reader);
            }
        }

        private void useReader(System.Xml.XmlTextReader reader)
        {
            while (reader.Read())
            {
                parseNode(reader);
            }
        }

        /// <summary>
        /// String content based Constructor
        /// </summary>
        /// <param name="osddXml">string to load the description document contents from</param>
        public OpenSearchDescriptionDocument(string osddXml)
        {
            System.IO.StringReader sr = new System.IO.StringReader(osddXml);
            try
            {
                System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(sr);
                useReader(reader);
            }
            finally
            {
                sr.Dispose();
            }
        }

        private void parseNode(System.Xml.XmlTextReader reader)
        {
            switch (reader.NodeType)
            {
                case System.Xml.XmlNodeType.Element:
                    parseElementNode(reader);
                    break;

                case System.Xml.XmlNodeType.XmlDeclaration:
                case System.Xml.XmlNodeType.Whitespace:
                case System.Xml.XmlNodeType.Comment:
                default:
                    break;
            }
        }

        private void parseElementNode(System.Xml.XmlTextReader reader)
        {
            if (reader.NamespaceURI != "http://a9.com/-/spec/opensearch/1.1/")
            {
                // This is not one of our Elements
                return;
            }
            switch (reader.LocalName)
            {
                case "ShortName":
                    shortName = reader.ReadString();
                    break;

                case "Description":
                    description = reader.ReadString();
                    break;

                case "Tags":
                    tags = new Collection<string>(parseTags(reader.ReadString()));
                    break;

                case "Contact":
                    contact = reader.ReadString();
                    break;

                case "Url":
                    Urls.Add(new Url(reader));
                    break;

                case "LongName":
                    longName = reader.ReadString();
                    break;

                case "Developer":
                    developer = reader.ReadString();
                    break;

                case "Attribution":
                    Attribution = reader.ReadString();
                    break;

                case "SyndicationRight":
                    syndicationRight = new SyndicationRights(reader);
                    break;

                case "AdultContent":
                    ParseAdultContent(reader.ReadString());
                    break;

                case "Image":
                    images.Add(new Image(reader));
                    break;

                case "Query":
                    queries.Add(new Query(reader));
                    break;

                case "Language":
                    Languages.Add(reader.ReadString());
                    break;

                case "OutputEncoding":
                    outputEncodings.Add(reader.ReadString());
                    break;

                case "InputEncoding":
                    inputEncodings.Add(reader.ReadString());
                    break;
            }
        }

        private void ParseAdultContent(string adultContentString)
        {
            if ((adultContentString.Equals("false", StringComparison.OrdinalIgnoreCase)) ||
                (adultContentString.Equals("no", StringComparison.OrdinalIgnoreCase)) ||
                (adultContentString == "0"))
            {
                adultContent = false;
            }
            else
            {
                adultContent = true;
            }
        }

        private static List<string> parseTags(string tagsString)
        {
            if (tagsString.Contains(','))
            {
                // WORKAROUND: This is a "seen in the wild", but non-complaint usage
                return tagsString.Split(',').Select(tag => tag.Trim()).ToList<string>();
            }
            else
            {
                // handle tags per the spec
                return tagsString.Split(' ').ToList<string>();
            }
        }

        /// <summary>
        /// Brief human-readable title that identifies the search service
        /// </summary>
        /// <remarks>
        /// <para>
        /// The specification requires that this contain 16 or fewer characters of plain text, and no HTML or other markup.
        /// Those requirements are not enforced by this property.
        /// </para>
        /// </remarks>
        public string ShortName
        {
            get
            {
                return shortName;
            }
        }

        /// <summary>
        /// Human-readable description of the search service
        /// </summary>
        /// <remarks>
        /// <para>
        /// The specification requires that this contain 1024 or fewer characters of plain text, and no HTML or other markup.
        /// Those requirements are not enforced by this property.
        /// </para>
        /// </remarks>
        public string Description
        {
            get
            {
                return description;
            }
        }

        /// <summary>
        /// Set of words that are used as keywords to identify and categorise this search content.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Each tag is a single word.
        /// </para>
        /// </remarks>
        public ICollection<string> Tags
        {
            get { return tags; }
        }

        /// <summary>
        /// Email address at which the maintainer of the description document can be reached
        /// </summary>
        /// <remarks>
        /// <para>
        /// The email address format is (by specification), an <a href="http://tools.ietf.org/html/rfc2822#section-3.4.1">RFC2822 Addr-spec</a>. RFC 2822 was obsoleted by <a href="http://tools.ietf.org/html/rfc5322#section-3.4.1">RFC 5322</a>.
        /// </para>
        /// </remarks>
        public string Contact
        {
            get { return contact; }
        }

        /// <summary>
        /// Description of interfaces by which a client can make requests for external resources
        /// </summary>
        public ICollection<Url> Urls
        {
            get { return urls; }
        }

        /// <summary>
        /// Extended human-readable title that identifies this search service
        /// </summary>
        /// <remarks>
        /// <para>
        /// The specification requires that this contain 48 or fewer characters of plain text, and no HTML or other markup.
        /// Those requirements are not enforced by this property.
        /// </para>
        /// </remarks>
        public string LongName
        {
            get { return longName; }
        }

        /// <summary>
        /// Images that can be used in association with this search content
        /// </summary>
        public ICollection<Image> Images
        {
            get { return images; }
        }

        /// <summary>
        /// Search queries that can be performed by search clients
        /// </summary>
        public ICollection<Query> Queries
        {
            get { return queries; }
        }

        /// <summary>
        /// Human-readable name or identifier of the creator or maintainer of the description document
        /// </summary>
        /// <remarks>
        /// <para>
        /// The developer is the person or entity that created the description document, and may or may not be the owner, author
        /// or copyright holder of the source of the content itself.
        /// </para>
        /// <para>
        /// The specification requires that this contain 64 or fewer characters of plain text, and no HTML or other markup.
        /// Those requirements are not enforced by this property.
        /// </para>
        /// </remarks>
        public string Developer
        {
            get { return developer; }
        }

        /// <summary>
        /// List of all sources or entitites that should be credited for the content contained in the search feed
        /// </summary>
        /// <remarks>
        /// <para>
        /// The specification requires that this contain 256 or fewer characters of plain text, and no HTML or other markup.
        /// Those requirements are not enforced by this property.
        /// </para>
        /// </remarks>
        public string Attribution { get; set; }

        /// <summary>
        /// Value that indicates the degree to which the search results provided by this search service can be queried, displayed and redistributed
        /// </summary>
        public SyndicationRights SyndicationRight
        {
            get { return syndicationRight; }
        }

        /// <summary>
        /// Whether the search results may contain material intended only for adults
        /// </summary>
        /// <remarks>
        /// <para>
        /// The OpenSearch specification notes that there are no universally applicable guidelines as to what constitutes
        /// "adult" content.
        /// </para>
        /// </remarks>
        public bool AdultContent
        {
            get
            {
                return adultContent;
            }
        }

        /// <summary>
        /// Indication that search server supports search results in the specified language
        /// </summary>
        /// <remarks>
        /// <para>
        /// The search provider should provide one language identifier for each language supported by the search provider.
        /// </para>
        /// <para>
        /// Language identifiers must be either "*" (indicating support for queries in arbitrary language), or an <a href="http://tools.ietf.org/html/rfc5646">RFC5646</a> language identifier.
        /// </para>
        /// </remarks>
        public ICollection<string> Languages
        {
            get { return languages; }
        }

        /// <summary>
        /// Indication that the search server supports search responses encoded with the specified character encoding
        /// </summary>
        /// <remarks>
        /// <para>
        /// The encoding must conform to <a href="http://www.w3.org/TR/2004/REC-xml-20040204/#charencoding">XML 1.0 Character Encodings</a> as specified
        /// by the <a href="http://www.iana.org/assignments/character-sets/character-sets.xhtml">IANA Character Set Assignment</a>.
        /// </para>
        /// <para>The default encoding is UTF-8.</para>
        /// </remarks>
        public ICollection<string> OutputEncodings
        {
            get { return outputEncodings; }
        }

        /// <summary>
        /// Indication that the search server supports search requests encoded with the specified character encoding
        /// </summary>
        /// <remarks>
        /// <para>
        /// The encoding must conform to <a href="http://www.w3.org/TR/2004/REC-xml-20040204/#charencoding">XML 1.0 Character Encodings</a> as specified
        /// by the <a href="http://www.iana.org/assignments/character-sets/character-sets.xhtml">IANA Character Set Assignment</a>.
        /// </para>
        /// <para>The default encoding is UTF-8.</para>
        /// </remarks>
        public ICollection<string> InputEncodings
        {
            get { return inputEncodings; }
        }
    }
}